package jmine.tec.services.input.processor;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jmine.tec.di.metadata.DependencyMetaData;
import jmine.tec.di.resolver.ObjectResolver;
import jmine.tec.di.resolver.impl.RegexMatchingResolver;
import jmine.tec.di.resolver.impl.SuffixOfObjectResolver;
import jmine.tec.di.resolver.impl.WithPreffixResolver;
import jmine.tec.services.ServicesController;
import jmine.tec.services.io.ServiceBean;
import jmine.tec.services.io.ServicesPageBean;
import bancosys.tec.services.implementor.ServiceImplementorAccessor;
import bancosys.tec.services.input.DefaultServiceFactory;
import bancosys.tec.services.input.driver.bean.ServicePageHeader;
import bancosys.tec.services.input.driver.processor.AbstractServiceProcessor;

/**
 * Processor de XML de servico com tags.
 * 
 * @author Marcio Ribeiro (mmr)
 * @created Sep 13, 2011
 */
public class ServiceXmlTagProcessor extends AbstractServiceProcessor<ServicePageHeader> {

    private ServicesController servicesController;

    private Map<String, ServiceDesc> serviceDescs;

    /**
     * @return valor do campo servicesController.
     */
    public ServicesController getServicesController() {
        return servicesController;
    }

    /**
     * Define valor para campo servicesController.
     * 
     * @param servicesController valor para campo servicesController.
     */
    public void setServicesController(ServicesController servicesController) {
        this.servicesController = servicesController;
    }

    /**
     * Service descriptor. Indica o que um servico precisa para trabalhar.
     * 
     * @author Marcio Ribeiro (mmr)
     * @created Sep 13, 2011
     */
    private final class ServiceDesc {
        private final ServiceImplementorAccessor serviceAcessor;

        // private final Class<?> serviceClass;

        private final Map<String, String> fields = new HashMap<String, String>();

        /**
         * Construtor.
         * 
         * @param s s.
         */
        private ServiceDesc(ServiceImplementorAccessor s) {
            this.serviceAcessor = s;
            // this.serviceClass = s.getImplementorType();
            this.populateFields();
        }

        /**
         * Popula campos.
         */
        private void populateFields() {
            // // Trata @ServiceFields
            // ServiceFields sf = this.serviceClass.getAnnotation(ServiceFields.class);
            // if (sf != null) {
            // for (String f : sf.required()) {
            // this.fields.put(normalize(f), f);
            // }
            // for (String f : sf.optional()) {
            // this.fields.put(normalize(f), f);
            // }
            // }
            //
            // // Trata dependencias de acessor
            for (DependencyMetaData dep : this.serviceAcessor.getAllDependencies()) {
                if (shouldNormalize(dep)) {
                    this.fields.put(normalize(dep.getPropertyName()), dep.getPropertyName());
                }
            }
        }

        /**
         * @param dep dependencia.
         * @return <code>true</code> se deve normalizar, <code>false</code> caso contrario.
         */
        private boolean shouldNormalize(DependencyMetaData dep) {
            ObjectResolver r = dep.getResolver();
            return !(r instanceof RegexMatchingResolver || r instanceof WithPreffixResolver || r instanceof SuffixOfObjectResolver);
        }

        /**
         * Estripa espacos e transforma acentos em sua contraparte sem acento.
         * 
         * @param n string a ser normalizada.
         * @return string normalizada.
         */
        private String normalize(String n) {
            String comAcentos = "àáâãäåèéêëìíîïòóôõöùúûüÀÁÂÃÄÅÈÉÊËÌÍÎÏÒÓÔÕÖÙÚÛÜçÇñÑß¢Ðð£Øø§µÝý¥¹²³ªº";
            String semAcentos = "aaaaaaeeeeiiiiooooouuuuAAAAAAEEEEIIIIOOOOOUUUUcCnNBcDdLOoSuYyY123ao";
            String r = n.replaceAll("[\\s._]*", "");
            for (int i = 0; i < comAcentos.length(); i++) {
                r = r.replaceAll(String.valueOf(comAcentos.charAt(i)), String.valueOf(semAcentos.charAt(i)));
            }
            return r;
        }

        /**
         * @param param nome do campo normalizado.
         * @return campo como escrito no servico para o param normalizado (sem acentos e espaco).
         */
        String getFieldByParam(String param) {
            return this.fields.get(param);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<ServicesPageBean> createServicePages(ServicePageHeader imp) {
        if (this.serviceDescs == null) {
            this.createServiceDescs();
        }

        for (ServicesPageBean page : imp.getWorkSheets()) {
            for (ServiceBean s : page.getServices()) {
                this.updateService(s);
            }
        }
        return imp.getWorkSheets();
    }

    /**
     * Cria service descriptors.
     */
    private void createServiceDescs() {
        this.serviceDescs = new HashMap<String, ServiceDesc>();
        DefaultServiceFactory dsf = (DefaultServiceFactory) this.servicesController.getServiceFactory();
        for (ServiceImplementorAccessor s : dsf.getServicesDefinition()) {
            this.serviceDescs.put(s.getActionType() + "." + s.getServiceName(), new ServiceDesc(s));
        }
    }

    /**
     * Atualiza servico com campos com acentos e caracteres especiais que não podem aparecer no nome de uma tag.
     * 
     * @param s servico.
     */
    private void updateService(ServiceBean s) {
        ServiceDesc desc = this.serviceDescs.get(s.getAction() + "." + s.getName());
        if (desc == null) {
            // Servico nao encontrado. Deixa o executor tratar o erro.
            return;
        }

        Map<String, Object> oldParams = s.getParameters();
        Map<String, Object> newParams = new HashMap<String, Object>();
        for (String param : oldParams.keySet()) {
            String field = desc.getFieldByParam(param);
            if (field == null) {
                // Campo nao encontrado ou campo dinamico (WithRegex, WithPreffix, etc)
                newParams.put(param, oldParams.get(param));
            } else {
                newParams.put(field, oldParams.get(param));
            }
        }
        s.setParameters(newParams);
    }
}
